解决多层嵌套滑动冲突 您所在的位置:网站首页 nestedscrollview viewpager2 解决多层嵌套滑动冲突

解决多层嵌套滑动冲突

2023-08-23 00:30| 来源: 网络整理| 查看: 265

  CoordinatorLayout作为顶层布局与NestedScrollView配合使用,可以用来协调子View的嵌套滑动。但是,如果要在CoordinatorLayout的外层嵌套XRefreshView下拉刷新控件,并且NestedScrollView嵌套多种可滑动的控件,这时候就出现了滑动冲突,具体嵌套结构如下所示:

1534665037068.jpg

XML布局结构大致如下:

......

  对于这些滑动冲突,我们该如何解决呢?下面我们就来逐一分析,解决这些滑动冲突。

  1、XRefreshView嵌套CoordinatorLayout

  我们知道事件分发是从上往下传递的(从Activity->Window->DecorView->......View),所以我们是不是可以在需要下拉刷新的时候,将事件拦截,交给下拉刷新控件处理,其他时候都不拦截事件。那么我们要选择用内部拦截法还是外部拦截法来处理滑动冲突呢?

  外部拦截法,那我们就需要在XRefreshView的onInterceptTouchEvent进行处理。XRefreshView是一个开源控件,从源码中,我们可以看到XRefreshView并未覆写onInterceptTouchEvent,而是覆写了dispatchTouchEvent方法,并在方法中进行了一系列复杂的事件分发。若使用外部拦截法来处理,就需要理清XRefreshView原有的事件分发规则,根据我们的实际需求对源码进行修改,对于父容器需要的事件进行拦截。

  内部拦截法,自定义CoordinatorLayout,覆写dispatchTouchEvent方法,通过调用requestDisallowInterceptTouchEvent方法来干扰父容器对事件的拦截。从XRefreshView的源码中我们发现该控件提供了一个disallowInterceptTouchEvent方法,从方法注释可知,子View需要事件的时候,可设置为true,不允许父容器拦截触摸事件。

XRefreshView.java

/** //XRefreshView.java * if child need the touch event,pass true */ public void disallowInterceptTouchEvent(boolean isIntercept) { mIsIntercept = isIntercept; }

  相比两种方法,这里内部拦截法更简单,所以最终选择了内部拦截法。

  触发下拉刷新的时机:垂直向下滑动,appBarLayout完全展开状态,允许XRefreshView拦截事件。

//MyCoordinatorLayout.java private int mLastX,mLastY; @Override public boolean dispatchTouchEvent(MotionEvent ev) { //内部拦截法 int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: if(xRefreshView!=null){ xRefreshView.disallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; if(Math.abs(deltaY)>Math.abs(deltaX)){ // if(observed!=null && observed.getAppBarLayoutStatus() == 1 && deltaY>0 && xRefreshView!=null && isTop()){ // //垂直向下滑动,appBarLayout展开状态,列表第一个item可见,将事件交还给父容器XRefreshView // xRefreshView.disallowInterceptTouchEvent(false); // } // 注意:若此时item位置不是第一个可见时,不能下拉刷新,若不需要item位置第一个可见时就可以下拉刷新,可以把isTop判断去掉,若下所示: if(observed!=null && observed.getAppBarLayoutStatus() == 1 && deltaY>0 && xRefreshView!=null){ //垂直向下滑动,appBarLayout展开状态,将事件交还给父容器XRefreshView xRefreshView.disallowInterceptTouchEvent(false); }else{ //判断触摸点是否落在banner上 bannerView = getBannerView(); if(bannerView!=null){ isTouchPointInBannerView = Util.calcViewScreenLocation(bannerView).contains(ev.getRawX(),ev.getRawY()); }else{ isTouchPointInBannerView = false; } } } break; case MotionEvent.ACTION_UP: isTouchPointInBannerView = false; break; default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(ev); }

  2、NestedScrollView嵌套多种布局控件(LinearLayout、SmartTabLayout、ViewPager,ViewPager中又嵌套了RecyclerView)

  这里主要解决的不是滑动冲突,而是NestedScrollView嵌套ViewPager无法显示的问题。网上的解决方案有2种,一是重写ViewPager的onMeasure方法,遍历每个页面,获取最高的页面高度来设置ViewPager的高度,二是给NestedScrollView设置android:fillViewport="true"允许 NestedScrollView中的组件去充满它。对于第一种方案,存在一个问题,每个页面的内容高度都一样,并且滑动其中一个页面的列表时,其他页面的列表也会滑动,所以这里采用了方案二。

  在SmartTablayout上方我们设置了一个LinearLayout,这个LinearLayout可以用来作为广告布局的一个父容器。当滑动NestedScrollView时,这个LinearLayout需要可以往屏幕外滑出,直到smartTabLayout保持在置顶位置。那要怎么让LinerLayout可以滑出屏幕直至不可见呢?答案是增大可滑动的空间,在原来内容高度的基础上增加广告布局父容器的高度。我们知道,NestedScrollView不能直接嵌套多个布局,只能有一个直接子类(允许直接嵌套的子类有RecyclerVIew、ViewPager、LinearLayout),这里选择在线性布局里嵌套多个布局,并且自定义这个直接子类,重新去计算它的高度(在系统计算的高度上,加上允许滑出屏幕的高度),具体如下所示:

xml布局:

...... ......

VerticalLinearLayout.java

public class VerticalLinearLayout extends LinearLayout{ private int maxOffsetY; public VerticalLinearLayout(Context context) { super(context); init(); } public VerticalLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public VerticalLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int specSize = MeasureSpec.getSize(heightMeasureSpec); View child; int childCount = getChildCount(); int offset = 0; for(int i=0;i0 && nestedScrollView !=null){ //NestedScrollView存在最大滑出屏幕的偏移量时,需要对dy消耗进行处理 if(dy>0){ //上滑 if(nsvMaxOffsetY == nestedScrollView.getScrollY()){ //banner隐藏时,不消耗dy,交给列表,列表滑动dy consumed[1] = 0; }else{ //banner可见 //触摸点在RecyclerView上时,设置父容器消耗dy,不让列表滑动,同时滚动NestedScrollView consumed[1] = dy; nestedScrollViewScrollBy(0,dy); } }else{ //下滑 if(nestedScrollView.getScrollY() == nsvMaxOffsetY){ //banner隐藏 if(isTop()){ //列表第一个item可见,设置父容器消耗dy,不让列表滑动,同时滚动NestedScrollView consumed[1] = dy; nestedScrollViewScrollBy(0,dy); }else{ //列表第一个item不可见,父容器不消耗dy,交给RecyclerView消耗dy consumed[1] = 0; } }else if(nestedScrollView.getScrollY()>0){ //banner可见未完全展开 //触摸点在RecyclerView上时,设置父容器消耗dy,不让列表滑动,同时滚动NestedScrollView consumed[1] = dy; nestedScrollViewScrollBy(0,dy); } } } } }

那么多层嵌套滑动冲突的解决到这里就结束了。源码地址:https://github.com/lmz14/NestedScrollDemo

版权声明: 转载请注明出处



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有